package com.jinyang.jetpackdemo.util.http

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.jinyang.jetpackdemo.BuildConfig
import com.jinyang.jetpackdemo.util.LjyLogUtil
import com.jinyang.jetpackdemo.util.LjyToastUtil
import com.jinyang.jetpackdemo.util.NetUtil
import com.jinyang.jetpackdemo.util.application.ApplicationUtil
import com.jinyang.jetpackdemo.util.gson.GsonUtils
import com.jinyang.jetpackdemo.util.http.interceptor.LoggingInterceptor
import com.jinyang.jetpackdemo.util.http.model.BaseHttpResponse
import com.jinyang.jetpackdemo.util.http.model.Parameter
import com.jinyang.jetpackdemo.util.http.model.TrustAllCerts
import com.zhy.http.okhttp.cookie.CookieJarImpl
import com.zhy.http.okhttp.cookie.store.PersistentCookieStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import okhttp3.*
import okio.*
import org.json.JSONObject
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.*
import java.io.FileNotFoundException
import java.io.IOException
import java.net.Proxy
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.HashMap
import java.util.concurrent.TimeUnit

/**
 * @Author:         LiuJinYang
 * @CreateDate:     2021/12/3
 * @Description:
 */
object RetrofitUtils {
    private var apiService: ApiService

    init {
        val retrofit = Retrofit.Builder()
            .baseUrl(BaseUrl.baseUrl) //配置okhttp
            .client(getOkHttpClient()) //支持gson
            .addConverterFactory(GsonConverterFactory.create()) //增加返回值为Oservable<T>的支持,RxJava
            .build()
        apiService = retrofit.create(ApiService::class.java)
    }

    /**
     * OkHttpClient配置
     */
    private fun getOkHttpClient(): OkHttpClient {
        var builder = OkHttpClient.Builder()
        val cookieJar =
            CookieJarImpl(PersistentCookieStore(ApplicationUtil.instance.getAppContext()))
        builder.cookieJar(cookieJar) //持久化cookie,保持session会话：com.zhy:okhttputils:2.6.2
        //添加header
        builder.addInterceptor(getHeaderInterceptor())
        //添加公参的拦截器
        builder.addInterceptor(getBasicInterceptor())
        //设置连接超时
        builder.connectTimeout(mConnectTimeout.toLong(), TimeUnit.SECONDS) //设置读超时
            .readTimeout(mReadTimeout.toLong(), TimeUnit.SECONDS) //设置写超时
            .writeTimeout(mWriteTimeout.toLong(), TimeUnit.SECONDS)
        if (BuildConfig.IS_DEBUG) {
            //缓存拦截器
            //        builder.addNetworkInterceptor(new CacheInterceptor(context));
            //日志拦截器
            builder.addInterceptor(LoggingInterceptor())
            //信任证书，便于调试时抓包
            builder = builder.sslSocketFactory(
                TrustAllCerts.createSSLSocketFactory(),
                TrustAllCerts()
            )
                .hostnameVerifier(TrustAllCerts.TrustAllHostnameVerifier())
        } else {
            builder.proxy(Proxy.NO_PROXY)
        }
        return builder.build()
    }

    /**
     * get请求， 返回结果data为对象
     */
    fun <T> ViewModel.get(
        methodPath: String, params: Parameter?, tClass: Class<T>,
        resultList: Boolean = false,
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        viewModelScope.launch {
            get(methodPath, params).catch { e ->
                var errorCode = HTTP_CODE_FAIL_CODE
                var errorMsg = TIPS_FAIL
                if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                    errorCode = HTTP_CODE_NO_NET
                    errorMsg = TIPS_NO_NET
                }
                LjyToastUtil.getInstance().toast(errorMsg)
                LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
                onError(errorCode, errorMsg)
            }.collect {
                doCallBack(tClass, resultList, it, onSuccess, onError)
            }
        }
    }

    suspend fun get(
        methodPath: String, params: Parameter?
    ): Flow<BaseHttpResponse<Any?>?> {
        return flow {
            emit(apiService.get(methodPath, params))
        }.flowOn(Dispatchers.IO)
    }

    suspend fun <T> get(
        methodPath: String, params: Parameter?, tClass: Class<T>,
        resultList: Boolean = false,
    ): Result<T> {
        return try {
            doCallBack(tClass, resultList, apiService.get(methodPath, params))
        } catch (e: Exception) {
            var errorCode = HTTP_CODE_FAIL_CODE
            var errorMsg = TIPS_FAIL
            if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                errorCode = HTTP_CODE_NO_NET
                errorMsg = TIPS_NO_NET
            }
            LjyToastUtil.getInstance().toast(errorMsg)
            LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
            Result.Error(errorCode, errorMsg)
        }
    }

    /**
     * post请求，参数为json， 返回结果data为对象
     */
    fun <T> ViewModel.postParamsJson(
        methodPath: String, params: Parameter?, tClass: Class<T>,
        resultList: Boolean = false,
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        viewModelScope.launch {
            flow {
                emit(apiService.postJson(methodPath, params))
            }.flowOn(Dispatchers.IO).catch { e ->
                var errorCode = HTTP_CODE_FAIL_CODE
                var errorMsg = TIPS_FAIL
                if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                    errorCode = HTTP_CODE_NO_NET
                    errorMsg = TIPS_NO_NET
                }
                LjyToastUtil.getInstance().toast(errorMsg)
                onError(errorCode, errorMsg)
                LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
            }.collect {
                doCallBack(tClass, resultList, it, onSuccess, onError)
            }
        }
    }

    /**
     * post请求，参数为form， 返回结果data为对象
     */
    fun <T> ViewModel.postParamsForm(
        methodPath: String, params: Parameter?, tClass: Class<*>,
        resultList: Boolean = false,
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        viewModelScope.launch {
            flow {
                emit(apiService.postForm(methodPath, params))
            }.flowOn(Dispatchers.IO).catch { e ->
                var errorCode = HTTP_CODE_FAIL_CODE
                var errorMsg = TIPS_FAIL
                if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                    errorCode = HTTP_CODE_NO_NET
                    errorMsg = TIPS_NO_NET
                }
                LjyToastUtil.getInstance().toast(errorMsg)
                onError(errorCode, errorMsg)
                LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
            }.collect {
                doCallBack(tClass, resultList, it, onSuccess, onError)
            }
        }
    }

    fun <T> ViewModel.postParamsJsonString(
        methodPath: String?, params: String?,
        tClass: Class<*>,
        resultList: Boolean = false,
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), params)
        viewModelScope.launch {
            flow {
                emit(apiService.postJsonStr(methodPath, body))
            }.flowOn(Dispatchers.IO).catch { e ->
                var errorCode = HTTP_CODE_FAIL_CODE
                var errorMsg = TIPS_FAIL
                if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                    errorCode = HTTP_CODE_NO_NET
                    errorMsg = TIPS_NO_NET
                }
                LjyToastUtil.getInstance().toast(errorMsg)
                onError(errorCode, errorMsg)
                LjyLogUtil.d(e.message)
                LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
            }.collect {
                doCallBack(tClass, resultList, it, onSuccess, onError)
            }
        }
    }


    fun ViewModel.download(
        url: String?,
        file: File?,
        listener: (bytesRead: Long, contentLength: Long, success: Boolean) -> Unit,
        onSuccess: (msg: String?, data: InputStream?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        val clientBuilder = OkHttpClient.Builder()
            .sslSocketFactory(TrustAllCerts.createSSLSocketFactory(), TrustAllCerts())
            .hostnameVerifier(TrustAllCerts.TrustAllHostnameVerifier())
        clientBuilder.addInterceptor(ProgressInterceptor(listener))
        clientBuilder.retryOnConnectionFailure(true)
            .connectTimeout(60, TimeUnit.SECONDS) //设置读超时
            .readTimeout(120, TimeUnit.SECONDS) //设置写超时
            .writeTimeout(120, TimeUnit.SECONDS)

        val retrofit = Retrofit.Builder()
            .baseUrl(BaseUrl.baseUrl)
            .client(clientBuilder.build())
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val apiServiceDown = retrofit.create(ApiService::class.java)
        viewModelScope.launch {
            flow {
                emit(apiServiceDown.downloadFile(url))
            }
                .map {
                    it.byteStream()
                }
                .onEach {
                    saveFile(it, file)
                }
                .flowOn(Dispatchers.IO)
                .catch { e ->
                    var errorCode = HTTP_CODE_FAIL_CODE
                    var errorMsg = TIPS_FAIL
                    if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                        errorCode = HTTP_CODE_NO_NET
                        errorMsg = TIPS_NO_NET
                    }
                    LjyToastUtil.getInstance().toast(errorMsg)
                    onError(errorCode, errorMsg)
                }
                .collect {
                    onSuccess(null, it)
                }
        }
    }

    fun ViewModel.uploadFiles(
        methodPath: String,
        imageFile: File,
        tClass: Class<*>,
        resultList: Boolean = false,
        attachInfoType: String = "12",
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        val requestBody: RequestBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("attachInfoType", attachInfoType)
            .addFormDataPart(
                "file",
                imageFile.name,
                RequestBody.create(MediaType.parse("image/*"), imageFile)
            )
            .build()
        viewModelScope.launch {
            flow {
                emit(apiService.uploadFile(methodPath, requestBody))
            }.flowOn(Dispatchers.IO).catch { e ->
                var errorCode = HTTP_CODE_FAIL_CODE
                var errorMsg = TIPS_FAIL
                if (!NetUtil.isNetworkAvailable(ApplicationUtil.instance.getAppContext())) {
                    errorCode = HTTP_CODE_NO_NET
                    errorMsg = TIPS_NO_NET
                }
                LjyToastUtil.getInstance().toast(errorMsg)
                onError(errorCode, errorMsg)
                LjyLogUtil.d("errorCode: " + errorCode + "__errorMsg: " + errorMsg)
            }.collect {
                doCallBack(tClass, resultList, it, onSuccess, onError)
            }
        }
    }

    private fun <T> doCallBack(
        tClass: Class<T>,
        resultList: Boolean,
        response: BaseHttpResponse<Any?>?,
    ): Result<T> {
        return if (response == null) {
            LjyToastUtil.getInstance().toast(TIPS_FAIL)
            LjyLogUtil.d("errorCode: " + HTTP_CODE_FAIL_CODE + "__errorMsg: " + TIPS_FAIL)
            Result.Error(HTTP_CODE_FAIL_CODE, TIPS_FAIL)
        } else {
            if (response.code == HTTP_CODE_SUCCESS_CODE || response.code == HTTP_CODE_SUCCESS_CODE_NEW) {
                if (response.data == null) {
                    Result.Success(response.message, null)
                } else {
                    val data = GsonUtils.toJson(response.data)
                    if (resultList) {
                        val result = GsonUtils.fromJson(data, tClass)
                        Result.Success(response.message, result)
                    } else {
                        val result = GsonUtils.fromJson(data, tClass)
                        Result.Success(response.message, result)
                    }
                }
            } else {
                if (response.code != HTTP_CODE_NO_DATA // && response.getCode() != HTTP_CODE_SYSTEM_MAINTAINING
                    && response.code != HTTP_CODE_BAD_TOKEN
                ) {
                    LjyToastUtil.getInstance().toast(response.message)
                }
                LjyLogUtil.d("errorCode: " + response.code + "__errorMsg: " + response.message)
                if (response.code == HTTP_CODE_BAD_TOKEN) {
                    //token失效

                } else if (response.code == HTTP_CODE_SYSTEM_MAINTAINING) {
                    //维护中

                }
                Result.Error(response.code ?: 0, response.message)
            }
        }
    }

    private fun <T> doCallBack(
        tClass: Class<T>,
        resultList: Boolean,
        response: BaseHttpResponse<Any?>?,
        onSuccess: (msg: String?, data: Any?) -> Unit,
        onError: (errorCode: Int, errorMsg: String?) -> Unit
    ) {
        if (response == null) {
            onError(HTTP_CODE_FAIL_CODE, TIPS_FAIL)
            LjyToastUtil.getInstance().toast(TIPS_FAIL)
            LjyLogUtil.d("errorCode: " + HTTP_CODE_FAIL_CODE + "__errorMsg: " + TIPS_FAIL)
        } else {
            if (response.code == HTTP_CODE_SUCCESS_CODE || response.code == HTTP_CODE_SUCCESS_CODE_NEW) {
                if (response.data == null) {
                    onSuccess(response.message, null)
                } else {
                    val data = GsonUtils.toJson(response.data)
                    if (resultList) {
                        val result = GsonUtils.fromJson2Array(data, tClass)
                        onSuccess(response.message, result)
                    } else {
                        val result = GsonUtils.fromJson<Any>(data, tClass)
                        onSuccess(response.message, result)
                    }
                }
            } else {
                onError(response.code ?: 0, response.message)
                if (response.code != HTTP_CODE_NO_DATA // && response.getCode() != HTTP_CODE_SYSTEM_MAINTAINING
                    && response.code != HTTP_CODE_BAD_TOKEN
                ) {
                    LjyToastUtil.getInstance().toast(response.message)
                }
                LjyLogUtil.d("errorCode: " + response.code + "__errorMsg: " + response.message)
                if (response.code == HTTP_CODE_BAD_TOKEN) {
                    //token失效

                } else if (response.code == HTTP_CODE_SYSTEM_MAINTAINING) {
                    //维护中

                }
            }
        }
    }

    fun saveFile(`in`: InputStream, file: File?) {
        try {
            val out = FileOutputStream(file)
            val buff = ByteArray(1024)
            var len: Int
            while (`in`.read(buff).also { len = it } != -1) {
                out.write(buff, 0, len)
            }
            `in`.close()
            out.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /**
     * 添加header的拦截器
     */
    private fun getHeaderInterceptor(): Interceptor {
        return Interceptor { chain: Interceptor.Chain ->
            val builder = chain.request()
                .newBuilder()
            for ((key, value) in headerMap) {
                builder.addHeader(key, value)
            }
            chain.proceed(builder.build())
        }
    }

    /**
     * 配置公参的拦截器
     */
    private fun getBasicInterceptor(): Interceptor {
        return Interceptor { chain: Interceptor.Chain ->
            val jsonObject = JSONObject()
            val paramsMapGet: MutableMap<String, String> = HashMap()
            val paramsMapPost: MutableMap<String, String> = HashMap()
            if (basicMap != null && basicMap.size > 0) {
                try {
                    for ((key, value) in basicMap) {
                        jsonObject.put(key, value)
                    }
                    paramsMapGet[URLEncoder.encode("basic", "utf-8")] =
                        URLEncoder.encode(jsonObject.toString(), "utf-8")
                    paramsMapPost["basic"] = jsonObject.toString()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            val request = chain.request()
            val builder = request
                .newBuilder()
            if ("GET" == request.method()) {
                val httpUrlBuilder = request.url().newBuilder()
                for ((key, value) in paramsMapGet) {
                    httpUrlBuilder.addEncodedQueryParameter(key, value)
                }
                builder.url(httpUrlBuilder.build())
            } else if ("POST" == request.method()) {
                if (request.body() is FormBody) {
                    val newFormBodyBuilder = FormBody.Builder()
                    for ((key, value) in paramsMapPost) {
                        newFormBodyBuilder.add(key, value)
                    }
                    val oldFormBody = request.body() as FormBody?
                    val paramSize = oldFormBody!!.size()
                    if (paramSize > 0) {
                        for (i in 0 until paramSize) {
                            newFormBodyBuilder.add(oldFormBody.name(i), oldFormBody.value(i))
                        }
                    }
                    builder.post(newFormBodyBuilder.build())
                } else if (request.body() is MultipartBody) {
                    val multipartBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
                    for ((key, value) in paramsMapPost) {
                        multipartBuilder.addFormDataPart(key, value)
                    }
                    val oldParts = (request.body() as MultipartBody?)!!.parts()
                    if (oldParts != null && oldParts.size > 0) {
                        for (part in oldParts) {
                            multipartBuilder.addPart(part)
                        }
                    }
                    builder.post(multipartBuilder.build())
                } else {
                    val requestBody = request.body()
                    var body: String? = null
                    if (requestBody != null) {
                        val buffer = Buffer()
                        requestBody.writeTo(buffer)
                        var charset = StandardCharsets.UTF_8
                        val contentType = requestBody.contentType()
                        if (contentType != null) {
                            charset = contentType.charset(StandardCharsets.UTF_8)
                        }
                        body = buffer.readString(charset)
                    }
                    //拼接参数
                    val rootMap: MutableMap<String, String> =
                        Gson().fromJson(
                            body,
                            MutableMap::class.java
                        ) as MutableMap<String, String>
                    rootMap["basic"] = jsonObject.toString() //以json字符串形式添加公参
                    //                        rootMap.put("basic", basicMap);//以对象形式添加公参
                    val newJsonParams = Gson().toJson(rootMap)

                    //request 的重新构建
                    val JSON = MediaType.parse("application/json; charset=utf-8")
                    builder.post(RequestBody.create(JSON, newJsonParams))
                }
            }
            chain.proceed(builder.build())
        }
    }
}

/**
 * 请求成功
 */
const val HTTP_CODE_SUCCESS_CODE = 0

const val HTTP_CODE_SUCCESS_CODE_NEW = 200

/**
 * 查询不到数据
 */

const val HTTP_CODE_NO_DATA = 1000

/**
 * token失效
 */
const val HTTP_CODE_BAD_TOKEN = 4007

/**
 * 系统维护中
 */
const val HTTP_CODE_SYSTEM_MAINTAINING = 9999

/**
 * 请求失败
 */
const val HTTP_CODE_FAIL_CODE = -1000

const val TIPS_FAIL = "系统繁忙，操作失败！"

/**
 * 没有网络
 */
const val HTTP_CODE_NO_NET = -2000

const val TIPS_NO_NET = "网络异常，请稍后重试！"


private var mConnectTimeout = 30
private var mReadTimeout = 60
private var mWriteTimeout = 60

/**
 * 设置超时时长，可以写在Application中
 *
 * @param connectTimeout 连接超时
 * @param readTimeout    读取超时
 * @param writeTimeout   写入超时
 */
fun setTimeOut(connectTimeout: Int, readTimeout: Int, writeTimeout: Int) {
    mConnectTimeout = connectTimeout
    mReadTimeout = readTimeout
    mWriteTimeout = writeTimeout
}

private val basicMap: HashMap<String, String> = HashMap()
private val headerMap = HashMap<String, String>()

/**
 * 添加header
 *
 * @param key
 * @param value
 */
fun addHeader(key: String, value: String) {
    headerMap[key] = value
}

/**
 * 添加公参basic
 *
 * @param key   公参字段名
 * @param value 公参值
 */
fun addBasic(key: String, value: String) {
    basicMap[key] = value
}

sealed class Result<out T> {
    data class Success<T>(val msg: String?, val data: T?) : Result<T>()
    data class Error<T>(val errorCode: Int, val errorMsg: String?) : Result<T>()
}

class ProgressInterceptor(private val listener: (bytesRead: Long, contentLength: Long, success: Boolean) -> Unit) :
    Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalResponse = chain.proceed(chain.request())
        return originalResponse.newBuilder()
            .body(ProgressResponseBody(originalResponse.body(), listener))
            .build()
    }
}

class ProgressResponseBody(
    private val responseBody: ResponseBody?,
    private val progressListener: (bytesRead: Long, contentLength: Long, success: Boolean) -> Unit
) :
    ResponseBody() {
    private var bufferedSource: BufferedSource? = null
    override fun contentType(): MediaType? {
        return responseBody?.contentType()
    }

    override fun contentLength(): Long {
        return responseBody?.contentLength()!!
    }

    override fun source(): BufferedSource? {
        if (bufferedSource == null) {
            bufferedSource = responseBody?.source()?.let { source(it).buffer() }
        }
        return bufferedSource
    }

    private fun source(source: Source): Source {
        return object : ForwardingSource(source) {
            var totalBytesRead = 0L

            @Throws(IOException::class)
            override fun read(sink: Buffer, byteCount: Long): Long {
                val bytesRead = super.read(sink, byteCount)
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                totalBytesRead += if (bytesRead != -1L) bytesRead else 0
                responseBody?.contentLength()?.let {
                    progressListener(
                        totalBytesRead,
                        it,
                        bytesRead == -1L
                    )
                }
                return bytesRead
            }
        }
    }
}